#!/usr/bin/env python
# coding=utf-8
import argparse
import signal
import sys
import time
import traceback
import json

from common.log import init, info, warn, debug, error
from common.rabbitmq_interface import RabbitmqService, pika
from common.utils import byteify
from common.aliyun_product_operation_interface import ALiyunSSHKeypairOperation
import aliyun_key_pair_db_model
from aliyun_key_pair_db_model import AliyunSSHKeyPairDBExecutor, AliyunSSHKeyPairBindDBExecutor
from common.setting import Cmop2ParametersAdapter, ConfigureDiscovery

ALIYUN_SERVER_LOG = "aliyun_ssh_keypair.log"
ALIYUN_SERVER_CONF = "service.conf"
COMPUTE_EXCHANGE = "vulcanus-iaas-computer"
LOG_EXCHANGE = "vulcanus-iaas-log"
TASK_ROUTE_KEY = "vulcanus.task.create"

def _signal_handler(signum, frame):
    info("System exit.")
    sys.exit(0)

def task_report(req, repo, route_key):
    task_rep = {}
    rs = RabbitmqService()
    task_rep["taskCode"] = repo["taskCode"]
    task_rep["action"] = route_key
    task_rep["username"] = repo["username"]
    task_rep["status"] = "Success" if repo["statusCode"] == "200" else "Error"
    task_rep["requestData"] = req
    task_rep["responseData"] = json.dumps(repo)
    if isinstance(repo["infos"]["requestId"], list):
        for request_id in repo["infos"]["requestId"]:
            task_rep["requestId"] = request_id
            tr_body = json.dumps(task_rep)
            rs.publish(exchange=LOG_EXCHANGE, routing_key=TASK_ROUTE_KEY, message=tr_body)
    else:
        task_rep["requestId"] = repo["infos"]["requestId"]
        tr_body = json.dumps(task_rep)
        rs.publish(exchange=LOG_EXCHANGE, routing_key=TASK_ROUTE_KEY, message=tr_body)

def rpc_query_common(func):

    def wrapper(method, properties, body):
        info("[x] Received %r" % (body))
        info("[x] Begin to do %s ..." % (method.routing_key))
        # publish message to vulcanus-iaas-log access successful
        rs = RabbitmqService()
        req = json.loads(body, object_hook=byteify)

        # convert the data to current format.
        cmop2_adapter = Cmop2ParametersAdapter()
        request_info = cmop2_adapter.input_adapt(req)

        repo = func(**request_info)
        response_info = cmop2_adapter.output_adapt(repo)

        rs.publish(exchange=COMPUTE_EXCHANGE, routing_key=properties.reply_to,
                   properties=pika.BasicProperties(correlation_id=properties.correlation_id),
                   message=json.dumps(response_info))

        # publish message to vulcanus-iaas-log with task report
        task_report(body, response_info, method.routing_key)

    return wrapper

@rpc_query_common
def rpc_query_keypair(ak, sk, region_id, **kwargs):
    aliy_op = ALiyunSSHKeypairOperation(ak, sk, region_id)
    repo = {}
    repo["Infos"] = aliy_op.DescribeKeyPairs(**kwargs)
    return repo

def rpc_process(ch, method, properties, body):

    # debug("Get rpc call body : %r" % (body))
    ALIYUN_RPC_CALLBACK_DICT = {
        "vulcanus.keypair.query.list": rpc_query_keypair
    }
    # report to log the rpc is began
    ch.basic_publish(exchange=LOG_EXCHANGE, routing_key="result.access.successful",
                     body="Accept rpc request successful!")

    if method.routing_key in ALIYUN_RPC_CALLBACK_DICT:
        evfunc = ALIYUN_RPC_CALLBACK_DICT[method.routing_key]
        evfunc(method, properties, body)
        # publish message to vulcanus-iaas-log execute complete
        ch.basic_publish(exchange=LOG_EXCHANGE, routing_key="result.access.successful",
                         body="Process %s request complete!" % (method.routing_key))
    else:
        msg = "There is no callback to process request %s." % (method.routing_key)
        ch.basic_publish(exchange=LOG_EXCHANGE, routing_key="result.access.failure",
                         body=msg)

    ch.basic_ack(delivery_tag=method.delivery_tag)

def event_process_common(func):
    def wrapper(method, properties, body):
        info("[x] Received %r" % (body))
        info("[x] Begin to do %s ..." % (method.routing_key))
        # publish message to vulcanus-iaas-log access successful
        rs = RabbitmqService()

        # get basic info
        req = json.loads(body, object_hook=byteify)

        # convert the data to current format.
        cmop2_adapter = Cmop2ParametersAdapter()
        request_info = cmop2_adapter.input_adapt(req)
        repo = {}
        try:
            repo = func(**request_info)
        except Exception as ex:
            repo["StatusCode"] = "Error"
            repo["Message"] = "Failed to execute %s with some exceptions %s." % \
                              (func.__name__, traceback.format_exc())
            repo["Infos"] = {}
            repo["RequestId"] = ""

        response_info = cmop2_adapter.output_adapt(repo)

        msg = json.dumps(response_info)

        # publish message to vulcanus-iaas-compute with execute result
        rs.publish(exchange=COMPUTE_EXCHANGE, routing_key="%s.result" % (method.routing_key),
                   message=msg)

        # publish message to vulcanus-iaas-log with task report
        task_report(body, response_info, method.routing_key)

    return wrapper

@event_process_common
def event_create_keypair(ak, sk, region_id, **kwargs):

    # do the action
    # debug("Get ak %s, sk %s, region id %s." % (ak, sk, region_id))
    # time.sleep(5)
    # ret = {"StatusCode":"200", "Message":"Successful", "InstanceId":"abcdefg", "RequestId":"123456"}
    aliy_op = ALiyunSSHKeypairOperation(ak, sk, region_id)
    aliy_db = AliyunSSHKeyPairDBExecutor()

    create_keypair_info = {"TaskCode":"", "KeyPairName":"", "RegionId":"", "KeyPairFingerprint":"",
                           "PrivateKeyBody":"", "PublicKeyBody":""}

    ret = aliy_op.CreateKeyPair(**kwargs)
    repo = dict(kwargs, **ret)

    if repo["StatusCode"] == "200":
        for k, v in repo.items():
            if k in create_keypair_info:
                create_keypair_info[k] = v

        aliy_db.insert(**create_keypair_info)
    else:
        error("Failed to create ssh key pair due to %s." % (repo["Message"]))

    return repo

@event_process_common
def event_import_keypair(ak, sk, region_id, **kwargs):
    # import keypair seem like create keypair.

    aliy_op = ALiyunSSHKeypairOperation(ak, sk, region_id)
    aliy_db = AliyunSSHKeyPairDBExecutor()

    create_keypair_info = {"TaskCode": "", "KeyPairName": "", "RegionId": "", "KeyPairFingerprint": "",
                           "PrivateKeyBody": "", "PublicKeyBody": ""}

    ret = aliy_op.ImportKeyPair(**kwargs)
    repo = dict(kwargs, **ret)

    if repo["StatusCode"] == "200":
        for k, v in repo.items():
            if k in create_keypair_info:
                create_keypair_info[k] = v

        aliy_db.insert(**create_keypair_info)
    else:
        error("Failed to import ssh key pair due to %s." % (repo["Message"]))

    return repo

@event_process_common
def event_delete_keypair(ak, sk, region_id, **kwargs):
    repo = {}
    aliy_op = ALiyunSSHKeypairOperation(ak, sk, region_id)
    ret = aliy_op.DeleteKeyPairs(**kwargs)
    repo = dict(kwargs, **ret)

    if repo["StatusCode"] == "200":
        aliy_db_skp = AliyunSSHKeyPairDBExecutor()
        aliy_db_skpb = AliyunSSHKeyPairBindDBExecutor()

        # delete ssh keypairs in the database
        # delete all of the information related to the key
        keypairnames = kwargs["KeyPairNames"]
        for keypairname in keypairnames:
            aliy_db_skp.delete(KeyPairName=keypairname)
            aliy_db_skpb.delete(KeyPairName=keypairname)
    else:
        error("Failed to delete ssh key pair due to %s." % (repo["Message"]))

    return repo

def _event_operation_common(op_func, **kwargs):
    ret = op_func(**kwargs)
    repo = dict(kwargs, **ret)

    return repo

@event_process_common
def event_attach_keypair(ak, sk, region_id, **kwargs):
    aliy_op = ALiyunSSHKeypairOperation(ak, sk, region_id)
    repo = _event_operation_common(aliy_op.AttachKeyPair, **kwargs)

    if repo["StatusCode"] == "200":
        # insert the relationship of binding.
        aliy_db_skpb = AliyunSSHKeyPairBindDBExecutor()
        instance_ids = json.loads(kwargs["InstanceIds"])
        for instance_id in instance_ids:
            bind_info = {"TaskCode":kwargs["TaskCode"], "InstanceId":instance_id,
                         "KeyPairName":kwargs["KeyPairName"]}
            aliy_db_skpb.insert(**bind_info)
    else:
        error("Failed to attach key pair due to %s." % (repo["Message"]))

    return repo

@event_process_common
def event_detach_keypair(ak, sk, region_id, **kwargs):
    aliy_op = ALiyunSSHKeypairOperation(ak, sk, region_id)
    repo = _event_operation_common(aliy_op.DetachKeyPair, **kwargs)

    if repo["StatusCode"] == "200":
        # delete the relationship of binding.
        aliy_db_skpb = AliyunSSHKeyPairBindDBExecutor()
        instance_ids = byteify(json.loads(kwargs["InstanceIds"]))
        for instance_id in instance_ids:
            aliy_db_skpb.delete(KeyPairName=kwargs["KeyPairName"],
                                InstanceId=instance_id)
    else:
        error("Failed to detach key pair due to %s." % (repo["Message"]))

    return repo

def instance_event_process(ch, method, properties, body):

    ALIYUN_EVENT_CALLBACK_DICT = {
        "vulcanus.keypair.create": event_create_keypair,
        "vulcanus.keypair.import": event_import_keypair,
        "vulcanus.keypair.delete": event_delete_keypair,
        "vulcanus.keypair.operation.attach": event_attach_keypair,
        "vulcanus.keypair.operation.detach": event_detach_keypair
    }

    ch.basic_publish(exchange=LOG_EXCHANGE, routing_key="result.access.successful",
                     body="accept %s request successful!" % (method.routing_key))

    if method.routing_key in ALIYUN_EVENT_CALLBACK_DICT:
        evfunc = ALIYUN_EVENT_CALLBACK_DICT[method.routing_key]
        evfunc(method, properties, body)
        # publish message to vulcanus-iaas-log execute complete
        ch.basic_publish(exchange=LOG_EXCHANGE, routing_key="result.access.successful",
                         body="process %s request complete!" % (method.routing_key))
    else:
        msg = "There is no callback to process request %s." % (method.routing_key)
        ch.basic_publish(exchange=LOG_EXCHANGE, routing_key="result.access.failure",
                         body=msg)

    ch.basic_ack(delivery_tag=method.delivery_tag)

def start_rabbitmq_mode(args):
    # consumer event create.instance
    # publish event create.instance.log
    # publish event create.instance.stats
    host = args.rabbitmq_host
    port = args.rabbitmq_port
    user = args.rabbitmq_user
    password = args.rabbitmq_password
    rs = RabbitmqService(host, port, user, password)

    # create a channel, if one message can't be ack
    # the queue will be ignore
    rs.channel_create(prefetch_count=1)

    # # create a rpc server
    # rs.create_rpc_server(queue_name="Vulcanus.ecs.query", rpc_callback=rpc_process)

    # create two exchanges
    # declare exchange
    rs.exchange_declare(exchange=COMPUTE_EXCHANGE, type="topic", durable=True)
    rs.exchange_declare(exchange=LOG_EXCHANGE, type="fanout", durable=True)

    # bind queue
    args = {"x-message-ttl": 60000}
    # related route keys
    # ecs.create
    # ecs.create.result
    # ecs.query
    # ecs.query.result
    # ecs.modify
    # bind queue for event process
    route_keys = ["vulcanus.keypair.create", "vulcanus.keypair.import",
                  "vulcanus.keypair.delete", "vulcanus.keypair.operation.attach",
                  "vulcanus.keypair.operation.detach"]
    q_name = "aliyun_keypair_service"
    rs.queue_bind(COMPUTE_EXCHANGE, route_keys=route_keys,
                  queue_name=q_name, durable=True,
                  arguments=args)
    rs.regist_consume(q_name, instance_event_process)

    # bind queue for rpc
    route_keys = ["vulcanus.keypair.query.list"]
    q_name = "aliyun_keypair_rpc"
    rs.queue_bind(COMPUTE_EXCHANGE, route_keys=route_keys,
                  queue_name=q_name, durable=True,
                  arguments=args)
    rs.regist_consume(q_name, rpc_process)

    # start consuming
    rs.start_consuming()
    signal.signal(signal.SIGINT, _signal_handler)
    rs.consuming_loop()

def argument_parser():
    parser = argparse.ArgumentParser(description="Aliyun ECS Operation CLI.")

    # create actions subcommand
    sparsers = parser.add_subparsers(title="Actions", description="Actions of the service.",
                                     help="Action name, use -h after it for more details.")
    # start rabbitmq server
    sparser_rabbitmq_conn = sparsers.add_parser("start-rabbitmq-to-connect",
                                                help="Start as a worker service for API Server events exchange. Note: if you have multiple events to confim and public plse use config file.")
    sparser_rabbitmq_conn.set_defaults(func=start_rabbitmq_mode)

    sparser_rabbitmq_conn.add_argument("-H", "--host", action="store", type=str, dest="rabbitmq_host",
                                       help="Rabbitmq server host to connect.", required=False, default="localhost")
    sparser_rabbitmq_conn.add_argument("-p", "--port", action="store", type=int, dest="rabbitmq_port",
                                       help="Rabbitmq server port to connect.", required=False, default=5672)
    sparser_rabbitmq_conn.add_argument("-u", "--user", action="store", type=str, dest="rabbitmq_user",
                                       help="Rabbitmq server user to connect.", required=False, default="guest")
    sparser_rabbitmq_conn.add_argument("-P", "--password", action="store", type=str, dest="rabbitmq_password",
                                       help="Rabbitmq server password to connect.", required=False, default="guest")
    sparser_rabbitmq_conn.add_argument("-c", "--config", action="store", type=str, dest="conf_path",
                                       help="Server config file.", required=False, default=ALIYUN_SERVER_CONF)
    sparser_rabbitmq_conn.add_argument("-d", "--daemon", action="store", type=bool, dest="daemon",
                                       help="Server daemonized flag.", required=False, default=False)
    sparser_rabbitmq_conn.add_argument("-l", "--log-path", action="store", type=str, dest="log_path",
                                       help="Server log path.", required=False, default=ALIYUN_SERVER_LOG)

    return parser.parse_args()

if __name__ == "__main__":
    args = argument_parser()
    init(logpath=args.log_path)
    cd = ConfigureDiscovery()
    cmop2_adapter = Cmop2ParametersAdapter()
    cd.regist_observer("input_mapping", cmop2_adapter.input_notify)
    cd.regist_observer("output_mapping", cmop2_adapter.output_notify)
    cd.regist_observer("db", aliyun_key_pair_db_model.notify)
    cd.start_observe_conf(conf_file=args.conf_path)
    cd.notify()
    args.func(args)
